Открытый курс по машинному обучению. Сессия № 2

</center> Автор материала: программист-исследователь Mail.ru Group, старший преподаватель Факультета Компьютерных Наук ВШЭ Юрий Кашницкий. Материал распространяется на условиях лицензии Creative Commons CC BY-NC-SA 4.0. Можно использовать в любых целях (редактировать, поправлять и брать за основу), кроме коммерческих, но с обязательным упоминанием автора материала.

Домашнее задание № 1

Анализ данных по сердечно-сосудистым заболеваниям

Идеальный просмотр Jupyter notebooks возможен только локально, GitHub и nbviewer могут неправильно отбражать формулы или картинки.

В задании предлагается с помощью Pandas ответить на несколько вопросов по данным о сердечно-сосудистых заболеваниях (качать данные не надо, они уже есть в репозитории). Данные использовались в соревновании Ml Boot Camp 5.

Заполните код в клетках (где написано "Ваш код здесь") и ответьте на вопросы в веб-форме.

В соревновании предлагалось определить наличие/отсутствие сердечно-сосудистых заболеваний (ССЗ) по результатам осмотра пациента.

Описание данных.

Объективные признаки:

  • Возраст (age)
  • Рост (height)
  • Вес (weight)
  • Пол (gender)

Результаты измерения:

  • Артериальное давление верхнее и нижнее (ap_hi, ap_lo)
  • Холестерин (cholesterol)
  • Глюкоза (gluc)

Субъективные признаки (со слов пациентов):

  • Курение (smoke)
  • Употребление алкоголя (alco)
  • Физическая активность (active)

Целевой признак (который интересно будет прогнозировать):

  • Наличие сердечно-сосудистых заболеваний по результатам классического врачебного осмотра (cardio)

Значения показателей холестерина и глюкозы представлены одним из трех классов: норма, выше нормы, значительно выше нормы. Значения субъективных признаков — бинарны.

Все показатели даны на момент осмотра.

Мы будем работать только с обучающей выборкой и с помощью Pandas проведем первичный анализ данных.

Из библиотек нам понадобятся только NumPy и Pandas.


In [67]:
import numpy as np
import pandas as pd

Считываем данные из CSV-файла в объект pandas DataFrame.


In [66]:
df = pd.read_csv('../../data/mlbootcamp5_train.csv', sep=';', 
                 index_col='id')

Посмотрим на первые 5 записей.


In [68]:
df.head()


Out[68]:
age gender height weight ap_hi ap_lo cholesterol gluc smoke alco active cardio
id
0 18393 2 168 62.0 110 80 1 1 0 0 1 0
1 20228 1 156 85.0 140 90 3 1 0 0 1 1
2 18857 1 165 64.0 130 70 3 1 0 0 0 1
3 17623 2 169 82.0 150 100 1 1 0 0 1 1
4 17474 1 156 56.0 100 60 1 1 0 0 0 0

Вопрос 1 (1 балл). Сколько мужчин и женщин представлено в этом наборе данных? Не было дано расшифровки признака "пол" (какому полу соответствует 1, а какому – 2 в признаке gender) – это определите, посмотрев также на рост при разумном предположении, что в среднем мужчины выше.

Варианты:

  • 45530 женщин и 24470 мужчин
  • 45530 мужчин и 24470 женщин
  • 45470 женщин и 24530 мужчин
  • 45470 мужчин и 24530 женщин

In [47]:
print("{0} {1} и {2} {3}".format(
    df[df['gender'] == 1]['height'].count(),
    "женщин" if df[df['gender'] == 1]['height'].mean() < df[df['gender'] == 2]['height'].mean() else "мужчин",
    df[df['gender'] == 2]['height'].count(),
    "женщин" if df[df['gender'] == 1]['height'].mean() > df[df['gender'] == 2]['height'].mean() else "мужчин"
))


45530 женщин и 24470 мужчин

Вопрос 2 (1 балл). Кто в среднем чаще указывает, что употребляет алкоголь – мужчины или женщины?

Варианты:

  • мужчины
  • женщины

In [48]:
print("женщины" if df[df['gender'] == 1]['alco'].mean() > df[df['gender'] == 2]['alco'].mean() else "мужчины")


мужчины

Вопрос 3 (1 балл). Во сколько раз (округленно, round) процент курящих среди мужчин больше, чем процент курящих среди женщин (по крайней мере, по этим анкетным данным)?

Варианты:

  • 4
  • 8
  • 12
  • 16

In [49]:
round((df[df['gender'] == 2]['smoke'].mean() * 100) / (df[df['gender'] == 1]['smoke'].mean() * 100))


Out[49]:
12.0

Вопрос 4 (1 балл). Вы наверняка заметили, что значения возраста какие-то странные. Догадайтесь, в чем здесь измеряется возраст, и ответьте, на сколько месяцев (примерно) отличаются медианные значения возраста курящих и некурящих.

Варианты:

  • 5
  • 10
  • 15
  • 20

In [50]:
round((df[df['smoke'] == 0]['age'].median() - df[df['smoke'] == 1]['age'].median()) / 365.25 * 12)


Out[50]:
20

Вопрос 5 (2 балла). В статье на Википедии про сердечно-сосудистый риск показана шкала SCORE для расчёта риска смерти от сердечно-сосудистого заболевания в ближайшие 10 лет. Вот она:

Давайте посмотрим на правый верхний прямоугольник, отображающий сегмент курящих мужчин в возрасте от 60 до 64 лет включительно. (Неочевидно, но тут для возраста и давления цифры означают верхнюю границу, и она не включается).

Видим 9-ку в левом нижнем углу этого прямоугольника и 47 – в правом верхнем. То есть если при этом систолическое (т.е. верхнее) артериальное давление – меньше 120 мм рт.ст., а уровень холестерина – 4 ммоль/л, то риск ССЗ оценивается примерно в 5 раз ниже, чем если бы давление лежало в интервале [160, 180), а холестерина было бы 8 ммоль/л.

Давайте посчитаем аналогичное значение, но на наших данных.

Уточнения:

  • Посчитайте признак age_years – возраст в годах, округлив до целых (round). Для данного примера отберите курящих мужчин от 60 до 64 лет включительно
  • Категории уровня холестрина на рисунке и в наших данных отличаются. Отображение значений на картинке в значения признака cholesterol следующее: 4 ммоль/л $\rightarrow$ 1, 5-7 ммоль/л $\rightarrow$ 2, 8 ммоль/л $\rightarrow$ 3.
  • Интересуют 2 подвыборки курящих мужчин возраста от 60 до 64 лет: первая с верхним артериальным давлением меньше 120 мм рт.ст. и концентрацией холестерина – 4 ммоль/л, а вторая – с верхним артериальным давлением от 160 до 180 мм рт.ст. (не включительно) и концентрацией холестерина – 8 ммоль/л.

Во сколько раз (округленно, round) отличаются доли больных людей в этих двух подвыборках? Посчитайте на наших данных.

Варианты:

  • 2
  • 3
  • 4
  • 5

In [82]:
df['age_years'] = round(df['age'] / 365.25) 

subdf = df[(df['age_years'] >= 60) & (df['age_years'] <= 64)]

subdf[(subdf['ap_hi'] < 120) & (subdf['cholesterol'] == 1)].shape, subdf[(subdf['ap_hi'] > 160) & (subdf['ap_hi'] < 180) & (subdf['cholesterol'] == 3)].shape


Out[82]:
((1299, 14), (63, 14))

Вопрос 6 (2 балла). Постройте новый признак – BMI (Body Mass Index). Для этого надо вес в килограммах поделить на квадрат роста в метрах. Нормальными считаются значения BMI от 18.5 до 25. Выберите верные утверждения.

Утверждения:

  • Медианный BMI по выборке лежит в пределах нормы
  • У женщин в среднем BMI выше, чем у мужчин
  • У здоровых в среднем BMI выше, чем у больных
  • В сегменте здоровых и непьющих мужчин BMI ближе к норме, чем в сегменте здоровых и непьющих женщин

In [76]:
df['BMI'] = df['weight'] / (df['height'] / 100) ** 2
print(
     df['BMI'].median() >= 18.5 and df['BMI'].median() <= 25, "- "
    "Медианный BMI по выборке лежит в пределах нормы", 
)

print(
     df[df['gender'] == 1]['BMI'].mean() > df[df['gender'] == 2]['BMI'].mean(), "- "
    "У женщин в среднем BMI выше, чем у мужчин", 
)

print(
    df[df['cardio'] == 0]['BMI'].mean() > df[df['cardio'] == 1]['BMI'].mean(), "- "
    "У здоровых в среднем BMI выше, чем у больных"
)

subdf = df[(df['alco'] == 0) & (df['cardio'] == 0)]
print(
    (subdf[subdf['gender'] == 2]['BMI'].mean()) < (subdf[subdf['gender'] == 1]['BMI'].mean()), "- "
    "В сегменте здоровых и непьющих мужчин BMI ближе к норме, чем в сегменте здоровых и непьющих женщин"
)


False - Медианный BMI по выборке лежит в пределах нормы
True - У женщин в среднем BMI выше, чем у мужчин
False - У здоровых в среднем BMI выше, чем у больных
True - В сегменте здоровых и непьющих мужчин BMI ближе к норме, чем в сегменте здоровых и непьющих женщин

Вопрос 7 (2 балла). Можно заметить, что данные не особо-то чистые, много в них всякой "грязи" и неточностей. Еще лучше мы это увидим, когда обсудим визуализацию данных.

Отфильтруйте следующие сегменты пациентов (считаем это ошибками в данных)

  • указанное нижнее значение артериального давления выше верхнего
  • рост строго меньше 2.5%-перцентили или строго больше 97.5%-перцентили (используйте pd.Series.quantile, если не знаете, что это такое – прочитайте)
  • вес строго меньше 2.5%-перцентили или строго больше 97.5%-перцентили

Этот вовсе не вся чистка данных, которую можно было проделать, но пока остановимся на этом.

Сколько процентов данных (округленно, round) мы выбросили?

Варианты:

  • 8
  • 9
  • 10
  • 11

In [78]:
bad_df = df[
    (df['ap_lo'] > df['ap_hi']) |
    (df['height'] < df['height'].quantile(.025)) |
    (df['height'] > df['height'].quantile(.975)) |
    (df['weight'] < df['weight'].quantile(.025)) |
    (df['weight'] > df['weight'].quantile(.975))
]

round( bad_df['age'].count() * 100 / df['age'].count() )


Out[78]:
10.0